package com.d4a5.mapping.core
{
	import flash.utils.Dictionary;

	public class MathParser
	{
		
		protected static var _symbols:Object;
		
		protected static var _isFirstTime:Boolean = true;
		protected static var _operatorMapping:Object;
		
		public static function exec( exp:String , lut:Object = null ) : Number {
			if( MathParser._isFirstTime ){
				MathParser._operatorMapping = new Object();
				MathParser._operatorMapping["+"] = MathParser._operatorMapping["-"] = 2;
				MathParser._operatorMapping["*"] = MathParser._operatorMapping["/"] = 1;
				MathParser._operatorMapping["^"] = 0;
			}
			
		
			
			
			MathParser._symbols = new Object();
			if( lut ){
				for( var key:String in lut ){
					MathParser._symbols[key] = lut[key];
				}
			}
			
			MathParser._symbols["*"] = function multiply( a:Number , b:Number ):Number { return a * b; };
			MathParser._symbols["+"] = function add( a:Number , b:Number ):Number { return a + b; };
			MathParser._symbols["-"] = function subtract( a:Number , b:Number ):Number { return a - b; };
			MathParser._symbols["/"] = function divide( a:Number , b:Number ):Number { return a / b; };
			MathParser._symbols["^"] = Math.pow;
			
			MathParser._symbols["abs"] = Math.abs;
			MathParser._symbols["acos"] = Math.acos;
			MathParser._symbols["asin"] = Math.asin;
			MathParser._symbols["atan"] = Math.atan;
			MathParser._symbols["ceil"] = Math.ceil;
			MathParser._symbols["cos"] = Math.cos;
			MathParser._symbols["exp"] = Math.exp;
			MathParser._symbols["floor"] = Math.floor;
			MathParser._symbols["log"] = Math.log;
			MathParser._symbols["pow"] = Math.pow;
			MathParser._symbols["round"] = Math.round;
			MathParser._symbols["sin"] = Math.sin;
			MathParser._symbols["sqrt"] = Math.sqrt;
			MathParser._symbols["tan"] = Math.tan;
			
			MathParser._symbols["PI"] = Math.PI;
			MathParser._symbols["E"] = Math.E;
			MathParser._symbols["LN10"] = Math.LN10;
			MathParser._symbols["LN2"] = Math.LN2;
			MathParser._symbols["LOG10E"] = Math.LOG10E;
			MathParser._symbols["LOG2E"] = Math.LOG2E;
			MathParser._symbols["SQRT1_2"] = Math.SQRT1_2;
			MathParser._symbols["SQRT2"] = Math.SQRT2;
			
			
			
			
			var mpo:MathParserObject = new MathParserObject();
			mpo.fullExpression = exp.replace( /(\t|\n|\r| )+/g , "" );
			mpo.currentPosition = 0;
			mpo.numChars = mpo.fullExpression.length;
			mpo.lastValue = Number.NaN;
			
			return MathParser.recurseParens( mpo );
		}
		
		
		protected static function recurseParens( mpo:MathParserObject ) : Number {
			var char:String;
			var exp:String = "";
			
			for( ; mpo.currentPosition < mpo.numChars; mpo.currentPosition++ ){
				char = mpo.fullExpression.charAt( mpo.currentPosition );
				
				if( char == "(" ){
					mpo.currentPosition++;
					exp += "|" + MathParser.recurseParens(mpo).toPrecision(20);
				}
				else if( char == ")" ){
					return MathParser.calculate( exp );
				}
				else{
					exp += char;
				}
			}
			
			return MathParser.calculate( exp );
		}
		
		
		protected static function calculate( exp:String ) : Number {	
			var currentString:String = "";
			var values:Array = new Array();
			var walkThruOperatorPrecedenceList:Boolean = false;
			var operatorPrecedenceList:Array = [new Array(),new Array(),new Array()];
			
			
		
			
			//walk through this expression
			var isaMathFunction:String;
			var numChars:int = exp.length;
			for( var i:int = 0; i < numChars; i++ ){
				var char:String = exp.charAt( i );
				
				// if this char is an operator
				if( MathParser._symbols[char] ){
					currentString = MathParser.addToArray( values , currentString , char , isaMathFunction );
					if( currentString != "" ){
						continue;
					}
					
					//add to the operator precedence list
					walkThruOperatorPrecedenceList = true;
					operatorPrecedenceList[MathParser._operatorMapping[char]].push( { a : values.length - 1 ,	b : values.length , operator : char } );
				}
				else{
					if( char == "|" ){//this next value has been here before
						if( MathParser._symbols[currentString] is Function ){// if the preceding chars are the name of a function
							isaMathFunction = currentString;
						}
						else if( currentString && isNaN( Number(currentString) ) == false ){//if we are doing something like 5(32) so it has no operator
							exp = exp.replace( "|" , "*" );
							i--;
						}
						else{
							exp = exp.replace( "|" , "" );
							i--;
						}
					}
					else{//if it is not an operator than it is part of a value
						currentString += char;
					}
				}
			}
			MathParser.addToArray( values , currentString , char , isaMathFunction );
			
			
			
			var value:Number;
			if( walkThruOperatorPrecedenceList ){
				
				// walk through operators based on order of precedence
				for( var j:int = 0; j < 3; j++ ){
					
					var operationsList:Array = operatorPrecedenceList[j] as Array;
					var numOperations:int = operationsList.length;
					
					//walk through pairs of terms and apply operator
					for( var k:int = 0; k < numOperations; k++ ){
						var op:Object = operationsList[k];
						var operation:Function = MathParser._symbols[ op.operator ] as Function;
						value = values[op.a] = values[op.b] = operation( values[op.a] , values[op.b] );
					}
					
				}
			}
			else{
				// just a single value in the parens 
				value = values[0];
			}
			
			return value;
		}
		
		
		protected static function addToArray( arr:Array , currentString:String , currentChar:String = null,  isaMathFunction:String = null  ) : String {
			if( isaMathFunction ){
				if( currentChar == "-" && isaMathFunction.length == currentString.length ){
					currentString += currentChar;
				}
				else{
					var mathFunctionParameter:String = currentString.substr( isaMathFunction.length );
					
					arr.push( MathParser._symbols[isaMathFunction]( Number(mathFunctionParameter) ) );
					currentString = "";
				}
			}
			else{
				arr.push( MathParser.determineValue( currentString )  );
				currentString = "";
			}
			
			return currentString;
		}
		
		
		protected static function determineValue( item:String ) : Object {
			var sym:Object = MathParser._symbols[item];
			var num:Number = ( sym ) ? Number( sym ): Number(item);
			return ((isNaN(num)) ? item : num);
		}
		
	}

	
}



	
class MathParserObject
{
	public var currentPosition:int;
	public var numChars:int;
	public var fullExpression:String;
	
	public var lastValue:Number;
}

	
